
#include <math.h>
#include "interpreter.h"

namespace assembler {

namespace {
// log fuction implementation which throws exceptions for invalid arguments.
double safe_log(double x) {
  if (x < 1e-10)
    throw InterpreterArithmeticException();
  return log(x);
}
}  // namespace

const char *Interpreter::ArithmeticFunctionNames[
  Interpreter::ArithmeticFunctionCount] = {
    "sin", "cos", "ln", "exp" };

double (*Interpreter::ArithmeticFunctionImpl[
  Interpreter::ArithmeticFunctionCount])(double) = {
    &sin, &cos, &log, &exp};

Interpreter::Interpreter(MachineState *state)
:state_(state) {
}

void Interpreter::Interpret(const Command &cmd) {
  double denominator;
  double call_arg, call_result;
  switch (cmd.type()) {
    case MOV:
      state_->set_reg(cmd.arg1().arg.register_id, ArgumentValue(cmd.arg2()));
      break;
    case ADD:
      state_->set_reg(cmd.arg1().arg.register_id,
        ArgumentValue(cmd.arg1()) + ArgumentValue(cmd.arg2()));
      break;
    case SUB:
      state_->set_reg(cmd.arg1().arg.register_id,
        ArgumentValue(cmd.arg1()) - ArgumentValue(cmd.arg2()));
      break;
    case MULT:
      state_->set_reg(cmd.arg1().arg.register_id,
        ArgumentValue(cmd.arg1()) * ArgumentValue(cmd.arg2()));
      break;
    case DIV:
      denominator = ArgumentValue(cmd.arg2());
      if (abs(denominator) < 1e-15)
        throw InterpreterArithmeticException();
      state_->set_reg(cmd.arg1().arg.register_id,
        ArgumentValue(cmd.arg1()) / denominator);
      break;
    case CALL:
      call_arg = state_->reg(0);
      call_result = CallArithmetic(cmd.arg1(), call_arg);
      state_->set_reg(0, call_result);
      break;
    case RET:
      throw InterpreterNotSupportedCommandException();
    case PUSH:
      state_->push(ArgumentValue(cmd.arg1()));
      break;
    case POP:
      state_->pop(cmd.arg1().arg.register_id);
  }
}

double Interpreter::ArgumentValue(const CommandArgument &arg) const {
  if (arg.type == ARGUMENT_CONSTANT)
    return arg.arg.constant_value;
  if (arg.type == ARGUMENT_REGISTER)
    return state_->reg(arg.arg.register_id);
  // Wrong argument, simply return zero.
  return 0.0;
}

double Interpreter::CallArithmetic(const CommandArgument &function_arg,
                                   double x) {
  for (int i = 0; i < ArithmeticFunctionCount; ++i)
    if (strncmp(ArithmeticFunctionNames[i], function_arg.arg.function.name,
      function_arg.arg.function.length) == 0) {
      return ArithmeticFunctionImpl[i](x);
    }
  throw InterpreterFunctionNotSupportedException();
}

}  // namespace assembler